# -*- coding: utf-8 -*-

"""
@author: Laowang
@contact: QQ:1125564921
@Created on: 2022/7/12
@Remark: 
"""

import requests
import json
import re
import ast
from typing import Any
from abc import ABCMeta, abstractmethod
from requests.adapters import HTTPAdapter
from requests.models import Response
from requests.utils import get_encoding_from_headers
from requests.exceptions import TooManyRedirects, InvalidSchema

from framework_strategy.gopy.gateways.xt.v3.spot.xt_api_cfg import XTPlatConfig
from framework_strategy.gopy.gateways.binance.binance_api_cfg import BinancePlatConfig
from framework_strategy.gopy.gateways.ftx.ftx_api_cfg import FtxPlatConfig
from framework_strategy.gopy.driver.core import OPRedis, RedisMixnis
from framework_strategy.gopy.gateways.settings import REDISSETTING, \
    FTXAPISETTING, BINANCEAPISETTING, XTAPISETTING
from framework_strategy.gopy.gateways.icd import ICD


"""
requests The adapter package

"""


class ResponseMeta(object):

    def __init__(self) -> None:
        self._response = Response()

    def set_status_code(self, code):
        self._response.status_code = code

    def set_headers(self, headers):
        self._response.headers = headers

    def set_encoding(self, encoding):
        self._response.encoding = encoding

    def set_raw(self, raw):
        self._response.raw = raw

    def set_request(self, request):
        self._response.request = request

    def set_connection(self, client):
        self._response.connection = client

    def set_json(self, json):
        self._response.json = json

    def set_content(self, content):
        self._response._content = content

    def set_url(self, url):
        if isinstance(url, bytes):
            self._response.url = url.decode('utf-8')
        else:
            self._response.url = url

    @property
    def response(self):
        return self._response


class ResponseAdapter(HTTPAdapter):
    """
    TODO | 未封装完善
    Adapter,  
    Function To remove cached data directly when the requested data is in the cache 
    """

    def json(self):
        """ """
        content = self.content.decode() if isinstance(
            self.content, bytes) else self.content
        return json.loads(content)

    def prepare_send(self, request, stream=False, timeout=None, verify=True,
                     cert=None, proxies=None):
        """ """
        return super().send(request, stream, timeout, verify, cert, proxies)

    def send(self, request, stream=False, timeout=None, verify=True,
             cert=None, proxies=None):
        """Builds a :class:`Response <requests.Response>` object from a urllib3
        response. This should not be called from user code, and is only exposed
        for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`

        :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
        :param resp: The urllib3 response object.
        :rtype: requests.Response
        """
        response = self.prepare_send(
            request, stream, timeout, verify, cert, proxies)
        return response

    def close(self) -> None:
        """ """
        pass


class DepthResponseMeta(ResponseAdapter, ResponseMeta):

    def _prepare_send(self, response_meta, request):
        response_meta.set_headers(request.headers)
        response_meta.set_connection(self)
        response_meta.set_encoding(get_encoding_from_headers(request.headers))
        response_meta.set_raw(request)
        response_meta.set_url(request.url)
        response_meta.set_request(request)

    def prepare_send(self, request, stream, timeout, verify, cert, proxies):
        # check redis
        body = request.headers.get('body')
        body = ast.literal_eval(body)
        exchange, method, market = body.get('exchange').lower(), \
            body.get('_api_method').lower(), \
            body.get('_api_market').lower()
        if all([exchange, method, market]):
            # TODO: verify data
            data = ICD.get(exchange, method, market)
            self.content = json.dumps(data).encode('utf-8')
            if data:
                # packing
                response_meta = ResponseMeta()
                self._prepare_send(response_meta, request)
                response_meta.set_status_code(200)
                response_meta.set_content(self.content)
                response_meta.set_json(self.json)
                return response_meta.response
        return super().prepare_send(request, stream, timeout, verify, cert, proxies)


class SessionAdapter(requests.Session):
    """ """

    def get_adapter(self, url):
        """
        Returns the appropriate connection adapter for the given URL.

        :rtype: requests.adapters.BaseAdapter

        """

        for (prefix, adapter) in self.adapters.items():
            prog = re.compile(prefix)
            if url.lower().startswith(prefix.lower()) or prog.match(url.lower()):
                return adapter

        # Nothing matches :-/
        raise InvalidSchema(
            "No connection adapters were found for {!r}".format(url))


class AdapterManager:
    """ 不用管他 """

    def __init__(self) -> None:

        requests.Session = SessionAdapter
        self._requests = requests
        self._session = self._requests.Session()

    def __call__(self, *args: Any, **kwds: Any) -> Any:
        return self._session

    def register(self, prefix, adapter):
        self._session.mount(prefix, adapter)

    def adapers(self):
        return self._session.adapters

    def __getattr__(self, name):
        return getattr(self._session, name)

    def set_requests(self, request):
        self._requests = request

    @property
    def request(self, requests):
        self._requests = requests


# Add the request to be adapted, N:M , 1 : 1
# url : quotationResponse | Adapter . xt - binance | router
Session = AdapterManager()
# config
Session.register(XTPlatConfig.GET_MARKET_CONFIG, DepthResponseMeta())
Session.register(BinancePlatConfig.GET_MARKET_CONFIG, DepthResponseMeta())
Session.register(FtxPlatConfig.GET_MARKET_CONFIG, DepthResponseMeta())
# depth
Session.register(XTPlatConfig.GET_DEPTH, DepthResponseMeta())
Session.register(BinancePlatConfig.GET_DEPTH, DepthResponseMeta())
Session.register(FtxPlatConfig.GET_DEPTH, DepthResponseMeta())
# ticker
Session.register(FtxPlatConfig.GET_TICKER, DepthResponseMeta())
Session.register(BinancePlatConfig.GET_TICKER, DepthResponseMeta())
# trade
Session.register(FtxPlatConfig.GET_TRADE, DepthResponseMeta())
Session.register(BinancePlatConfig.GET_TRADE, DepthResponseMeta())

request = Session()
